#include <cstdio>

#include <map>
#include <sstream>
#include <stdexcept>

#include "SortLastGenerator.h"

#include "utils.h"

using namespace session_mgr;

static char const* appNodeFormat =
"        appNode\n"
"        {\n"
"            connection\n"
"            {\n"
"                hostname \"<hostname>\"\n"
"                port <port>\n"
"            }\n"
"            pipe\n"
"            {\n"
"                window\n"
"                {\n"
"                    viewport [0 0 <width> <height>]\n"
"                    channel\n"
"                    {\n"
"                        name \"app_channel\"\n"
"                    }\n"
"                }\n"
"            }\n"
"        }\n";

static char const* nodeFormat =
"        node\n"
"        {\n"
"            connection\n"
"            {\n"
"                hostname \"<hostname>\"\n"
"                port <port>\n"
"            }\n"
"<pipes>"
"        }\n";

static char const* pipeFormat =
"            pipe\n"
"            {\n"
"                device <pipe_id>\n"
"                window\n"
"                {\n"
"                    viewport [0 0 <buffer_width> <buffer_height>]\n"
"                    attributes\n"
"                    {\n"
"                        hint_drawable FBO\n"
"                    }\n"
"                    channel\n"
"                    {\n"
"                        name \"render_channel<channel_id>\"\n"
"                    }\n"
"                }\n"
"            }\n";

static char const* inputFormat =
"                inputframe { name \"part<id>\" }\n";

static char const* outputFormat =
"                compound\n"
"                {\n"
"                    channel \"render_channel<id>\"\n"
"                    buffer [COLOR DEPTH]\n"
"                    range [<start> <end>]\n"
"                    outputframe { name \"part<id>\" }\n"
"                }\n";

static char const* viewFormat =
"            view\n"
"            {\n"
"                wall\n"
"                {\n"
"                    bottom_left  [-<view_width> -<view_height> -<view_distance>]\n"
"                    bottom_right [ <view_width> -<view_height> -<view_distance>]\n"
"                    top_left     [-<view_width>  <view_height> -<view_distance>]\n"
"                }\n"
"            }\n";

static char const* overallFormat =
"server\n"
"{\n"
"    config\n"
"    {\n"
"<appnode>"
"<nodes>"
"        layout\n"
"        {\n"
"            name \"default_layout\"\n"
"<view>"
"        }\n"
"        canvas\n"
"        {\n"
"            name \"default_canvas\"\n"
"            layout \"default_layout\"\n"
"            segment\n"
"            {\n"
"                channel \"app_channel\"\n"
"            }\n"
"        }\n"
"        compound\n"
"        {\n"
"            compound\n"
"            {\n"
"                channel (segment 0 view 0)\n"
"                buffer [COLOR DEPTH]\n"
"                task [CLEAR ASSEMBLE]\n"
"<inputs>"
"<outputs>"
"            }\n"
"        }\n"
"    }\n"
"}\n";

static std::string assign(std::string const& format, std::map<std::string, std::string> const& assignments)
{
    std::string result;
    for(std::string::const_iterator p = format.begin(), end = format.end(); p != end; ++p)
    {
        if(*p == '<')
        {
            ++p;
            std::string name;
            while(p != end && *p != '>')
            {
                name += *p;
                ++p;
            }
            if(p == end)
                throw std::runtime_error("Config format error: no matching \">\".");
            
            std::map<std::string, std::string>::const_iterator assignment = assignments.find(name);
            if(assignment == assignments.end())
                throw std::runtime_error("Config generation error: no available assignments.");
            
            result += assignment->second;
        }
        else
            result += *p;
    }
    return result;
}

template <typename T>
static inline std::string toStr(T const& val)
{
    std::ostringstream buf;
    buf << val;
    return buf.str();
}

static std::string ipToStr(uint32_t ip)
{
    char str[16];
    std::sprintf(str, "%d.%d.%d.%d",
                 ip >> 24,
                 (ip >> 16) & 0xff,
                 (ip >> 8) & 0xff,
                 ip & 0xff
                );
    return str;
}

static std::string makeAppNode(
    session_mgr::ClientConnection const& appNode,
    uint32_t width,
    uint32_t height
);

static std::string makeNode(session_mgr::ClientConnection const& node,
                            uint32_t buffer_width,
                            uint32_t buffer_height,
                            uint32_t channel_starting
                           );

static std::string makePipe(
    uint32_t pipe_id,
    uint32_t channel_id,
    uint32_t buffer_width,
    uint32_t buffer_height
);

static std::string makeInput(uint32_t id);

static std::string makeOutput(uint32_t id, uint32_t channels_count);

static std::string makeView(
    float view_width,
    float view_height,
    float view_distance
);

std::string makeAppNode(const session_mgr::ClientConnection& appNode, uint32_t width, uint32_t height)
{
    using namespace std;
    map<string, string> assignments;
    assignments["hostname"] = ipToStr(appNode.ipv4addr);
    assignments["port"] = toStr(appNode.port);
    assignments["width"] = toStr(width);
    assignments["height"] = toStr(height);
    return assign(appNodeFormat, assignments);
}

std::string makeNode(const session_mgr::ClientConnection& node, uint32_t buffer_width, uint32_t buffer_height, uint32_t channel_starting)
{
    using namespace std;
    map<string, string> assignments;
    
    assignments["hostname"] = ipToStr(node.ipv4addr);
    assignments["port"] = toStr(node.port);
    
    std::string pipes;
    for(uint32_t i = 0; i < node.card_count; ++i)
        pipes += makePipe(i, channel_starting + i, buffer_width, buffer_height);
    
    assignments["pipes"] = pipes;
    
    return assign(nodeFormat, assignments);
}

std::string makePipe(uint32_t pipe_id, uint32_t channel_id, uint32_t buffer_width, uint32_t buffer_height)
{
    using namespace std;
    map<string, string> assignments;
    
    assignments["pipe_id"] = toStr(pipe_id);
    assignments["channel_id"] = toStr(channel_id);
    assignments["buffer_width"] = toStr(buffer_width);
    assignments["buffer_height"] = toStr(buffer_height);
    
    return assign(pipeFormat, assignments);
}

std::string makeInput(uint32_t id)
{
    using namespace std;
    map<string, string> assignments;
    assignments["id"] = toStr(id);
    return assign(inputFormat, assignments);
}

std::string makeOutput(uint32_t id, uint32_t channels_count)
{
    using namespace std;
    map<string, string> assignments;
    assignments["id"] = toStr(id);
    
    assignments["start"] = toStr(id / (double)channels_count);
    assignments["end"] = toStr((id + 1) / (double)channels_count);
    
    return assign(outputFormat, assignments);
}

std::string makeView(float view_width, float view_height, float view_distance)
{
    using namespace std;
    map<string, string> assignments;
    
    insertInto(assignments)
    ("view_width",    toStr(view_width))
    ("view_height",   toStr(view_height))
    ("view_distance", toStr(view_distance))
    ;
    
    return assign(viewFormat, assignments);
}

std::string SortLastGenerator::generateConfig(const session_mgr::ClientConnection clients[], std::size_t clients_count)
{
    using namespace std;
    
    uint32_t channels_count = 0;
    for(size_t i = 0; i < clients_count; ++i)
        channels_count += clients[i].card_count;
    
    std::string nodes;
    for(uint32_t i = 0, channel_id = 0; i < clients_count; ++i)
    {
        nodes += makeNode(clients[i], bufferWidth, bufferHeight, channel_id);
        channel_id += clients[i].card_count;
    }
    
    std::string inputs;
    std::string outputs;
    for(uint32_t i = 0; i < channels_count; ++i)
    {
        inputs += makeInput(i);
        outputs += makeOutput(i, channels_count);
    }
    
    map<string, string> assignments;
    insertInto(assignments)
    ("appnode", makeAppNode(appNode, windowWidth, windowHeight))
    ("nodes", nodes)
    ("view", makeView(viewWidth, viewHeight, viewDepth))
    ("inputs", inputs)
    ("outputs", outputs)
    ;
    
    return assign(overallFormat, assignments);
}
